package cn.wolfcode.service.impl;


import cn.wolfcode.common.exception.BusinessException;
import cn.wolfcode.web.msg.SeckillCodeMsg;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 分布式锁 统一处理模板
 */
@Service
@Slf4j
public class DistributedLockSupport {

    @Resource(name = "LockScript")
    private RedisScript<Boolean> lockScript;
    @Resource(name = "unLockScript")
    private RedisScript<Boolean> unLockScript;

    private final StringRedisTemplate redisTemplate;

    private final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor;

    public DistributedLockSupport(StringRedisTemplate redisTemplate, ScheduledThreadPoolExecutor scheduledThreadPoolExecutor) {
        this.redisTemplate = redisTemplate;
        this.scheduledThreadPoolExecutor = scheduledThreadPoolExecutor;
    }


    public void lock(String lockKey,int timeOut,BusinessFunction businessFunction){
        String threadId= UUID.randomUUID().toString(); // 线程唯一标识
        ScheduledFuture<?> scheduledFuture = null; // WatchDog
        try {
            boolean locked; // 是否获取到锁
            int count = 0; // 最大重试次数
            AtomicInteger watchDogCont= new AtomicInteger();
            int maxCount = 5;
            do {
                // 尝试获取锁,使用lua脚本保证原子性
                locked = Boolean.TRUE.equals(redisTemplate.execute(lockScript, Collections.singletonList(lockKey), threadId, timeOut +""));
                if (locked) {
                    break;
                }
                TimeUnit.MILLISECONDS.sleep(100); // 休眠100毫秒, 重试
                if (count++ >= maxCount) {
                    log.error("[ 获取锁失败 ]  重试次数超过最大重试次数{}次", count);
                    throw new BusinessException(SeckillCodeMsg.SECKILL_ERROR);
                }
            } while (true);

            int delay=timeOut/2+1; // 延迟key的过期时间
            // 获取当前线程
            Thread currentThread = Thread.currentThread();

            scheduledFuture = scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> {
                // 判断是否还持有锁
                String value = redisTemplate.opsForValue().get(lockKey);
                if (!StringUtils.isEmpty(value) && Objects.equals(value, threadId) && watchDogCont.get() < maxCount) {
                    // 续期
                    log.info("[ WatchDog续期锁的过期时间 ]  key:{}, threadId:{}", lockKey, threadId);
                    redisTemplate.expire(lockKey, delay + 2, TimeUnit.SECONDS);
                    watchDogCont.getAndIncrement();
                }else {
                    log.info("[ WatchDog取消续期 ]  key:{}, threadId:{}", lockKey, threadId);
                    currentThread.interrupt(); // 取消WatchDog
                }
            }, delay, delay, TimeUnit.SECONDS); // 延迟delay秒执行, 每隔delay秒执行一次
            // 业务逻辑
            businessFunction.business();
        } catch (BusinessException e){
            log.error("[ 获取锁失败 ]  业务异常", e);
            throw e;
        } catch (Exception e) {
            log.error("[ 获取锁失败 ]  系统异常", e);
            throw new BusinessException(SeckillCodeMsg.SECKILL_ERROR);
        } finally {
            // 取消WatchDog
            if (scheduledFuture != null) {
                log.info("[ 主线程任务完成WatchDog取消 ]  key:{}, threadId:{}", lockKey, threadId);
                scheduledFuture.cancel(true); // 取消任务
            }
            // 释放锁只能由持有锁的线程来释放
            boolean equals= Boolean.TRUE.equals(redisTemplate.execute(unLockScript, Collections.singletonList(lockKey), threadId));
        }

    }


    @FunctionalInterface
    public static interface BusinessFunction{
        void business();
    }
}
